/*
 * this class implements observer and execute the queries sent by client to the observable server 
 */
package server;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import server.ImageSerializer;
import ocsf.server.ConnectionToClient;

/**
 * The Class DBcomunicator.
 *  this class implements observer and execute the queries sent by client to the observable server 
 *  @author Tomer Elkayam
 */
public class DBcomunicator implements Observer
{
	
	/** The client connections. */
	private ArrayList<ConnectionToClient> clientConnections;
	
	/** The con. */
	private Connection con;

	
	/**
	 * Instantiates a new DBcomunicator.
	 */
	public DBcomunicator()
	{
		 clientConnections = new ArrayList<ConnectionToClient>();
	}
	
	
	/* (non-Javadoc)
	 * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
	 */
	@Override
	public void update(Observable o, Object arg) 
	{
		String q ; //the query to execute
		ResultSet rs; //the result set
		
		//save the message received from client and get the connection to client
		String msg = new String(((ObMessage)arg).getMessageFromObservable());
		ConnectionToClient clnt= ((ObMessage)arg).getClient();
		
		//determine the message content and act accordingly
		if(msg.contains("#OS:Client connected"))
		{
			clientConnections.add(clnt);
			System.out.println("client: "+clnt.getInetAddress()+" has connected");
			return;
		}
		
		if(msg.contains("#OS:Client disconnected."))
		{
			long tid = clnt.getId();
			for(int i=0;i<clientConnections.size();i++)
				if( tid == clientConnections.get(i).getId())
				{
					clientConnections.remove(i);
					return;
				}
		}
		
		if(msg.contains("#OS:Listening exception.") || msg.contains("#OS:Server closed."))
		{
			System.exit(1);
		}
		
		//if arg is not a connection - it is a message from client , handle it 
		try
		{
			Statement stmt = con.createStatement();
			
			if(msg.startsWith("search"))
			{
				//search requested data and save result in RS
				System.out.println(msg.substring(6));
				rs = stmt.executeQuery(msg.substring(6));	
				
				//create a new serialized result set
				SerializedRS srs = new SerializedRS(rs);
				
				//if the search result is null - send a message to client
				if(srs.getRowCount() == 0)
				{
					System.out.println("#Server: Null search result");
					clnt.sendToClient(msgWraper("#Server: Null search result","MSG"));
				}
				
				else
				{	
					System.out.println("#Server: Search result");
					clnt.sendToClient(srs.getRowSet());
				}
			}
			
			//Add or delete or update an entry in a table
			if(msg.startsWith("adu"))
			{
				stmt.executeUpdate(msg.substring(3));
				System.out.println("#Server: Action success");
				clnt.sendToClient(msgWraper("#Server: Action success","MSG"));
			}
			
			//get all photos requested photo and send it to client
			if(msg.startsWith("getphoto"))
			{
				//allocate an ArrayList of HashMaps of String,String : each HashMap contains a "PHOTO" and "LAYER"+LNUMBER keys
				//	the keys direct to the serialized photos retrieved
				ArrayList<HashMap<String,String>> serializedPhotosAndTheirLayers = new ArrayList<HashMap<String,String>>();
				
				//get an array of queries 
				ArrayList<String> photoQuerys =  mySplit(msg.substring(8), '>');
				 
				 
				for(int i = 0; i < photoQuerys.size(); i++) 
				{
					//if the string has ended - exit the loop and send the message
					
					//get the photo layers
					ArrayList<String> layerQuerys = mySplit(photoQuerys.get(i),'<');
					
					System.out.println(layerQuerys.get(0));
					//get a requested photo - the first query in layerQueries is a photo search
					String photo = getSerializedImage(stmt,layerQuerys.get(0));
					
					//if retrieving one of the photos has failed - inform the client
					if(photo == null)
					{
						System.out.println("#Server: Action failed");
						clnt.sendToClient(msgWraper("#Server: Action failed","MSG"));
						return;
					}
					
					//allocate a HashMap and add the photo to it
					HashMap<String, String> aPhotoAndItsLayers = new HashMap<String,String>();
					aPhotoAndItsLayers.put("PHOTO", photo);
					
					for (int j = 1; j < layerQuerys.size(); j++) 
					{
						//get a photo layer
						System.out.println(layerQuerys.get(j));
						String query = layerQuerys.get(j);
						String layer = getSerializedImage(stmt,query);
						
						//if the photo do not have a layer of this type , set a **NOLAYER** value instead
						if(layer == null)
							layer = "**NOLAYER**";
						
						//set the layer name 
						int nameStart = query.indexOf("LayerName = '") + 13;
						String layerName = query.substring(nameStart,  query.length()-1);
						System.out.println(layerName);
						
						aPhotoAndItsLayers.put(layerName, layer);
					}
					
					 serializedPhotosAndTheirLayers.add(aPhotoAndItsLayers);
				}
				
				if(!deliverPhotoList(serializedPhotosAndTheirLayers , clnt))
				{
					//call message handler
					System.out.println("#Server: Action failed");
					clnt.sendToClient(msgWraper("#Server: Action failed","MSG"));
				}
				
			}
			
			//get a photo from client and it to archive
			if(msg.startsWith("addphoto"))
			{
				//get the query from the string
				String message = msg.substring(8);
				
				char[] dataFromClient =  message.toCharArray();
				
				int saveIndex = 0;
				String query = "";
				String data64 = null; 
				
				for (int i = 0; i <dataFromClient.length; i++) 
				{
					if(dataFromClient[i]=='<')
					{
						saveIndex = i;
						break; //break the loop
					}
					query+= String.valueOf(dataFromClient[i]);
				}
				
				data64 = message.substring(saveIndex+1, message.length());
				
				int filepathStart = 0;
				int filepathEnd;
				
				if(query.contains("INSERT"))
					filepathEnd = query.indexOf("INSERT" );
				else
					filepathEnd = query.indexOf("UPDATE" );
				
				String imageFilePath = query.substring(filepathStart,filepathEnd);
				query = query.substring(filepathEnd);
				System.out.println(imageFilePath + " image data size:"+dataFromClient.length);
				
				//create a serialized layer
				ImageSerializer layerSerializer = new ImageSerializer(imageFilePath,data64);
				layerSerializer.deserializePhoto(); 
				
				System.out.println(query);
				stmt.executeUpdate(query);
				
				clnt.sendToClient(msgWraper("#Server: Action success","MSG"));
			}
			
		}
		catch (SQLException e)
		{
			//call message handler
			try 
			{
				//call message handler
				System.out.println("#Server: Action failed");
				clnt.sendToClient(msgWraper("#Server: Action failed","MSG"));
			} 
			catch (IOException e1) 
			{
				e1.printStackTrace();
			}
			printSQLException(e); 
		}
		catch (IOException e) 
		{
			System.out.println("send to client has failed");
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	/**
	 * My split.
	 * my implementation to split method<br>
	 * i.e mySplit("PQ_LQ_LQ","_") will split to {"PQ","LQ","LQ"}
	 * @param source the source string
	 * @param regex the regex to split the string by
	 * @return an array list of the splitter string
	 */
	private ArrayList<String> mySplit(String source, char regex)
	{
		char [] tmp = source.toCharArray();
		ArrayList<String> allStrings = new ArrayList<String>();
		String res = "";
		
		for (int i = 0; i <tmp.length;i++) 
		{
			if((tmp[i]==regex) || (i+1 == tmp.length))
			{
				if(i+1 == tmp.length)
					res+= String.valueOf(tmp[i]);
				String q = new String(res);
				allStrings.add(q);
				res = "";	
			}
			else
				res+= String.valueOf(tmp[i]);
				
		}
		
		return allStrings;
	}
	
	/**
	 * Gets the serialized image.
	 * 
	 * @param stmt the satment connected to mysql.
	 * @param searchQuary the image search quary
	 * @return the serialized image
	 */
	private String getSerializedImage(Statement stmt,String searchQuary)
	{
		try{
		ResultSet rs;
		
		//get the link to file
		rs = stmt.executeQuery(searchQuary);
		
		//if retrieving the photo has failed - return null
		if(!rs.next())
			return null;
		
		//open the photo and serialize it
		String filePath = rs.getString("linktofile");
		String serializedPhoto = serializePhoto(filePath);
		return serializedPhoto;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**
	 * Serialize photo.<br>
	 * open the filepath and return a serialized photo.
	 *
	 * @param filePath the file path
	 * @return the string
	 */
	private String serializePhoto(String filePath)
	{
		//e.g. "C:/sgip/Desert.jpg"
		ImageSerializer imageSerializer = new ImageSerializer(filePath);
		
		return imageSerializer.serializePhoto();
	}
	
	
	/**
	 * Deliver photo list to client.
	 *
	 * @param toSend the serialized photo list to send
	 * @param client the client
	 * @return true, if successful
	 */
	private boolean deliverPhotoList(Object toSend, ConnectionToClient client)
	{
		try
		{
			//call message handler
			client.sendToClient(msgWraper("#Server: Image","PHOTO"));
			Thread.sleep(500);
			client.sendToClient(toSend);
			
			return true;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return false;
		}
		
	}
	
	
	/**
	 * Connect to MySql data base.
	 *
	 * @param scheme the MySql scheme
	 * @param username the MySql username
	 * @param password the MySql password
	 * @return true, if successful
	 */
	public boolean connectToDB(String scheme ,String username , String password )
	{
		try
		{
			//connect to MySql DB using JDBC and connection information from User
			Class.forName("com.mysql.jdbc.Driver").newInstance();
			con = DriverManager.getConnection("jdbc:mysql://localhost/"+scheme, username ,password);	
			return true;
		}
		catch (Exception E)
		{
			E.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Gets the DB connection.
	 *
	 * @return the DB connection
	 */
	public Connection getDBConnection ()
	{
		return con;
	}
	
	
	/**
	 * Msg wraper.
	 * wrap a message with an ArrayList of HashMaps (for casting in client)
	 *
	 * @param message the message to client
	 * @param key the HashMap key
	 * @return the ArrayList
	 */
	private ArrayList<HashMap<String,String>> msgWraper(String message , String key)
	{
		ArrayList<HashMap<String,String>> wrapedMsg = new ArrayList<HashMap<String,String>>(); 
		HashMap<String,String> h = new HashMap<String,String>();
		h.put(key, message);
		wrapedMsg.add(h);
		
		return wrapedMsg;
	}
	
	
	/**
	 * Prints the sql exception.
	 *
	 * @param ex the SQLException
	 * 
	 */
	public static void printSQLException(SQLException ex) 
	{
	    for (Throwable e : ex) 
	    {
	        if (e instanceof SQLException) 
	        {

	                e.printStackTrace(System.err);
	                System.err.println("SQLState: " +
	                    ((SQLException)e).getSQLState());

	                System.err.println("Error Code: " +
	                    ((SQLException)e).getErrorCode());

	                System.err.println("Message: " + e.getMessage());

	                Throwable t = ex.getCause();
	                while(t != null) 
	                {
	                    System.out.println("Cause: " + t);
	                    t = t.getCause();
	                }
	        }
	    }
	}
}
